package laboratoryQ.experiment.ui.reportage;

import java.awt.Color;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.SwingUtilities;

import laboratoryQ.experiment.IExperiment;
import laboratoryQ.experiment.IExperimentResult;
import laboratoryQ.experiment.IExperiment.IExpRunStatusHandler;
import laboratoryQ.experiment.reportage.ExperimentRes2ComponentBasedReportageModel;
import laboratoryQ.experiment.settings.IExperimentSettings;
import laboratoryQ.experiment.settings.ISettingsContainer;
import laboratoryQ.experiment.ui.UIUtils;

public class ExperimentReportage implements ActionListener, WindowListener, IExpRunStatusHandler {
	private IReportageModel reportageModel;
	
	private JFrame reportageFrame;
	private JSplitPane reportageComponent;
	private JTabbedPane viewPane = new JTabbedPane();
	private JComponent controlExperimentComponent;
	private JProgressBar experimentProgressBar = new JProgressBar();
	private double prevExperimentProgressPercent = -1.0;

	private ThreadPoolExecutor experimentRunExecutor = new ThreadPoolExecutor(
			1,
			1,
			Long.MAX_VALUE,
			TimeUnit.SECONDS,
			new LinkedBlockingQueue<Runnable>()
	);

	private Runnable setExperimentProgrBarStatusTask = new SetExperimentProgrBarStatusTask();

	public ExperimentReportage(IReportageModel reportageModel) {
		this.reportageModel = reportageModel;
	}
	
	public static ExperimentReportage newExperiment2componentReportage(
			IExperimentSettings experimentSettings,
			IExperimentResult2componentConverter result2diagramConverter
	) {
		return new ExperimentReportage(
				new ExperimentRes2ComponentBasedReportageModel(
						experimentSettings, 
						result2diagramConverter
				)
		);
	}

	public static ExperimentReportage newExperiment2componentReportage(
			ISettingsContainer settingsContainer,
			IExperimentResult2componentConverter result2diagramConverter
	) {
		return new ExperimentReportage(
				new ExperimentRes2ComponentBasedReportageModel(
						settingsContainer, 
						result2diagramConverter
				)
		);
	}
	
	public void initialise() {
		initReportageComponent();
		initReportageFrame();
	}

	public void showReportageFrame() {
		showReportageFrame(new String[] {});
	}
	
	public void showReportageFrame(String ... args) {
		if (reportageFrame == null) {
			initialise();
		}
		reportageFrame.setVisible(true);
		if (args.length > 0 && args[0].equalsIgnoreCase("autorun")) {
			actionPerformed(null);
		}
	}
	
	public JFrame getReportageFrame() {
		if (reportageFrame == null) {
			initialise();
		}
		return reportageFrame;
	}

	public JComponent getReportageComponent() {
		if (reportageFrame == null) {
			initialise();
		}
		return reportageComponent;
	}

	private void initReportageFrame() {
		reportageFrame = UIUtils.configureSingleComponentFrame(reportageComponent);
		
		JMenu frMenu = new JMenu("Experiment");
		frMenu.add(new ShowSettingsAction(reportageComponent, controlExperimentComponent));
		JMenuBar menubar = new JMenuBar();
		menubar.add(frMenu);
		reportageFrame.setJMenuBar(menubar);
		
		reportageFrame.addWindowListener(this);
		
		reportageFrame.setSize(1000, 700);
		reportageFrame.setTitle(reportageModel.getExperimentTitle());
	}
	
	private void initReportageComponent() {
		refreshViewPaneFromModel();
		
		controlExperimentComponent = new JPanel();
		controlExperimentComponent.setLayout(new GridBagLayout());
		GridBagConstraints gbc = UIUtils.getDefaultGBC();
		controlExperimentComponent.add(reportageModel.getSetupSettingsComponent(), gbc);
		gbc.gridy++;
		gbc.weighty = 0.0;
		JButton runButton = new JButton(">> Run >>");
		runButton.addActionListener(this);
		controlExperimentComponent.add(runButton, gbc);
		gbc.gridy++;
		controlExperimentComponent.add(experimentProgressBar, gbc);
		
		JSplitPane mainPane = new JSplitPane(
				JSplitPane.HORIZONTAL_SPLIT, 
				controlExperimentComponent, 
				viewPane
		);
		reportageComponent = mainPane;
	}

	private void refreshViewPaneFromModel() {
		int selected = viewPane.getSelectedIndex();
		selected = Math.max(0, selected);
		viewPane.removeAll();
		int viewsCount = reportageModel.getViewsCount();
		for (int i = 0; i < viewsCount; i++) {
			JPanel defPanel = new JPanel();
			defPanel.setLayout(new GridLayout(1, 1));
			defPanel.add(reportageModel.getViewComponentAt(i));
			defPanel.setBackground(Color.WHITE);
			viewPane.addTab(reportageModel.getViewTitleAt(i), defPanel);
		}
		if (selected < viewsCount) {
			viewPane.setSelectedIndex(selected);
		}
	}

	// ### ### ### ###
	
	@SuppressWarnings("serial")
	private static class ShowSettingsAction extends AbstractAction {
		private JComponent settingsComp;
		private JSplitPane main;
		public ShowSettingsAction(JSplitPane main, JComponent settingsComp) {
			super("Show/hide settings");
			this.main = main;
			this.settingsComp = settingsComp;
		}
		@Override
		public void actionPerformed(ActionEvent e) {
			settingsComp.setVisible(!settingsComp.isVisible());
			main.setVisible(false);
			if (settingsComp.isVisible()) {
				main.setLeftComponent(settingsComp);
			}
			main.setVisible(true);
		}
	}
	
	@Override 
	public void actionPerformed(ActionEvent e) {
		IExperimentSettings settings = reportageModel.getSettings();
		IExperiment experiment = settings.getExperiment();
		beginExperimentRun();
		RunExperimentTask runExperimentTask = new RunExperimentTask(
				experiment,
				settings, 
				this
		);
		experimentRunExecutor.execute(runExperimentTask);
	}

	@Override
	public void handleExperimentResult(IExperimentResult expResult) {
		stepsProcessed(100, 100);
		reportageModel.getExperimentResultHandler()
			.handleExperimentResult(expResult);
		SwingUtilities.invokeLater(
				new Runnable() {
					@Override
					public void run() {
						refreshViewPaneFromModel();
					}
				}
		);
	}
	
	@Override
	public void stepsProcessed(int processed, int total) {
		reportageModel.getExperimentResultHandler()
			.stepsProcessed(processed, total);
		double percent = ((double)processed) / total * 100.0;
		if (percent - 1.0 > prevExperimentProgressPercent || processed == total) {
			prevExperimentProgressPercent = percent;
			SwingUtilities.invokeLater(setExperimentProgrBarStatusTask);
		}
	}

	private void beginExperimentRun() {
		reportageModel.onExperimentRunStart();
		viewPane.setVisible(false);
		viewPane.setVisible(true);
		prevExperimentProgressPercent = -1.0;
	}
	
	private static class RunExperimentTask implements Runnable {
		private IExperiment experiment;
		private IExperimentSettings settings;
		private IExpRunStatusHandler handler;
		
		public RunExperimentTask(
				IExperiment experiment,
				IExperimentSettings settings, 
				IExpRunStatusHandler handler
		) {
			synchronized (this) {
				this.experiment = experiment;
				this.settings = settings;
				this.handler = handler;
			}
		}

		@Override
		public void run() {
			synchronized (this) {
				experiment.runExperiment(
						settings,
						handler
				);
			}
		}
	}

	private class SetExperimentProgrBarStatusTask implements Runnable {
		@Override
		public void run() {
			experimentProgressBar.setValue((int) prevExperimentProgressPercent);
		}
	}
	
	// WindowListener
	@Override 
	public void windowActivated(WindowEvent arg0) {}
	@Override
	public void windowClosed(WindowEvent arg0) {
		reportageModel.shutdown();
		experimentRunExecutor.shutdown();
	}
	@Override
	public void windowClosing(WindowEvent arg0) {}
	@Override
	public void windowDeactivated(WindowEvent arg0) {}
	@Override
	public void windowDeiconified(WindowEvent arg0) {}
	@Override
	public void windowIconified(WindowEvent arg0) {}
	@Override
	public void windowOpened(WindowEvent arg0) {}
}
